package mc

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/spf13/cast"
	"net/http"
	"strings"
)

type RenderType string

const (
	RenderTypeDefault      RenderType = ""
	RenderTypeHTML         RenderType = "HTML"
	RenderTypeJSON         RenderType = "JSON"
	RenderTypeIndentedJSON RenderType = "INDENTEDJSON"
	RenderTypeSecureJSON   RenderType = "SECUREJSON"
	RenderTypeJSONP        RenderType = "JSONP"
	RenderTypeAsciiJSON    RenderType = "ASCIIJSON"
	RenderTypeYAML         RenderType = "YAML"
	RenderTypeXML          RenderType = "XML"
	RenderTypeProtoBuf     RenderType = "PROTOBUF"
	RenderTypeString       RenderType = "STRING"
)

// 上下文
type Context struct {
	ModuleName         string            // 模块
	RealModuleName     string            // 实际使用的模块
	ControllerName     string            // 控制器
	RealControllerName string            // 实际使用的控制器
	ActionName         string            // 操作方法
	ModelName          string            // 模型
	Template           string            // 模版
	OtherParams        map[string]string // 路径上的其它参数
	Assign             *Assign           // 内容分配器
	*gin.Context
}

func NewContext(c *gin.Context) (ctx *Context) {
	// 去除伪静态后缀
	path := strings.Replace(strings.ToLower(c.Request.URL.Path), "."+option.Router.UrlHtmlSuffix, "", -1)
	// 拆分请求路径
	params := strings.Split(path, option.Router.UrlPathSep)
	l := 4 - len(params)
	if l >0 {
		params = append(params, []string{"","","",""}[:l]...)
	}
	otherParams := params[4:]

	ctx = &Context{
		ModuleName:         CamelToCase(params[1]),
		RealModuleName:     CamelToCase(params[1]),
		ControllerName:     CamelToCase(params[2]),
		RealControllerName: CamelToCase(params[2]),
		ActionName:         CamelToCase(params[3]),
		//ModelName:          CamelToCase(params[4]),
		Context:            c,
		Template:           "",
		OtherParams: make(map[string]string),
		Assign: &Assign{
			Result: map[string]interface{}{
				option.Response.CodeName:    option.Response.SuccessCodeValue,
				option.Response.MessageName: "",
				option.Response.DataName:    nil,
			},
		},
	}
	ctx.Assign.Context = ctx
	// 路径上的其它参数，如/system/user/list/a1/b1/a2/b2, 返回 {a1:b1,a2:b2}
	for i := 0; i< len(otherParams); i+=2 {
		ctx.OtherParams[otherParams[i]] = ""
		if len(otherParams) > i+1 {
			ctx.OtherParams[otherParams[i]] = otherParams[i+1]
		}
	}
	return
}

//  是否ajax请求
func IsAjax(c *gin.Context) bool {
	return c.GetHeader("X-Requested-With") == "XMLHttpRequest"
}

// 获取渲染格式
func GetRenderType(c *gin.Context) (renderType RenderType) {
	if rt, ok := c.Get("MC_RENDER_TYPE"); ok {
		renderType = rt.(RenderType)
	}
	return
}

// 设置渲染格式
func SetRenderType(c *gin.Context, renderType RenderType) {
	c.Set("MC_RENDER_TYPE", renderType)
}

// 获取错误结果
func GetErrResult(c *gin.Context) (err *Result) {
	if e, ok := c.Get("MC_ERROR_RESULT"); ok {
		err = e.(*Result)
	}
	return
}

// 设置错误结果
func SetErrResult(c *gin.Context, err *Result) {
	c.Set("MC_ERROR_RESULT", err)
}

// 获取跟踪错误
func GetStackErr(c *gin.Context) string {
	return  c.GetString("MC_STACK_ERR")
}

// 设置跟踪错误
func SetStackErr(c *gin.Context, err string) {
	c.Set("MC_STACK_ERR", err)
}


// 获取分页信息
func (ctx *Context) GetPage() (page int, limit int, offset int) {
	if ctx.Request.Method == "GET" {
		page = cast.ToInt(ctx.DefaultQuery(option.Request.PageName, "1"))
		limit = cast.ToInt(ctx.DefaultQuery(option.Request.PageSizeName, cast.ToString(option.Request.PageSizeValue)))
	} else {
		page = cast.ToInt(ctx.DefaultPostForm(option.Request.PageName, "1"))
		limit = cast.ToInt(ctx.DefaultPostForm(option.Request.PageSizeName, cast.ToString(option.Request.PageSizeValue)))
	}
	if page < 1 {
		page = 1
	}
	if limit < 1 {
		limit = option.Request.PageSizeValue
	}
	offset = (page - 1) * limit
	return
}

// 获取排序信息
func (ctx *Context) GetOrder() (order string) {
	if ctx.Request.Method == "GET" {
		order = ctx.DefaultQuery(option.Request.OrderName, "")
	} else {
		order = ctx.DefaultPostForm(option.Request.OrderName, "")
	}
	return
}

// 结果渲染
// @param httpStatus 页面状态码
// @param renderType 渲染模式（如果默认方式，则使用上下文，如果上下文也是默认，则判断是否为ajax提交)
// @param onlyOutData 仅输出Data字段数据（对于renderTypeHTML无效）
// @param template 渲染模板，如未指定，侧使用上下文指定的模板）
// @param assign 渲染的内容分配器，如未指定，则使用上下文指定的内容分配器
func (ctx *Context) Render(httpStatus int, renderType RenderType, onlyOutData bool, template string, assign *Assign) {
	if template == "" {
		template = ctx.Template
	}
	if assign == nil {
		assign = ctx.Assign
	}

	var output interface{}
	if onlyOutData {
		output = assign.Result[option.Response.DataName]
	} else {
		output = assign.Result
	}

	// 渲染类型判断
	rt := renderType
	if rt == RenderTypeDefault {
		rt = GetRenderType(ctx.Context)
	}
	if rt == RenderTypeDefault && IsAjax(ctx.Context) {
		rt = option.Response.AjaxRenderType
	}
	switch rt {
	case RenderTypeAsciiJSON:
		ctx.AsciiJSON(httpStatus, output)
	case RenderTypeIndentedJSON:
		ctx.IndentedJSON(httpStatus, output)
	case RenderTypeJSON:
		ctx.JSON(httpStatus, output)
	case RenderTypeJSONP:
		ctx.JSONP(httpStatus, output)
	case RenderTypeSecureJSON:
		ctx.SecureJSON(httpStatus, output)
	case RenderTypeYAML:
		ctx.YAML(httpStatus, output)
	case RenderTypeXML:
		ctx.XML(httpStatus, output)
	case RenderTypeProtoBuf:
		ctx.ProtoBuf(httpStatus, output)
	case RenderTypeString:
		ctx.String(httpStatus, fmt.Sprint(output))
	default:
		if template == "" {
			template = GetTemplateName(ctx)
		}
		ctx.HTML(httpStatus, template, assign)
	}
}

// 使用默认的结果格式输出
func (ctx *Context) AbortWithSuccess(result *Result) {
	if result == nil {
		result = &Result{}
	}
	// 响应代码
	httpStatus := http.StatusOK
	if result.HttpStatus != 0 {
		httpStatus = result.HttpStatus
	}

	// 302 跳转
	if result.HttpStatus == http.StatusFound && result.RedirectUrl != "" {
		ctx.Redirect(http.StatusFound, result.RedirectUrl)
		ctx.Abort()
		return
	} else {
		ctx.Assign.Result["redirect_url"] = result.RedirectUrl
		ctx.Assign.Result["redirect_time"] = result.RedirectTime
	}

	// 消息代码
	if result.Code != "" {
		ctx.Assign.Result[option.Response.CodeName] = result.Code
	}
	if IsEmpty(ctx.Assign.Result[option.Response.CodeName]) {
		ctx.Assign.Result[option.Response.CodeName] = option.Response.SuccessCodeValue
	}
	if ctx.Assign.Result[option.Response.CodeName] == option.Response.SuccessCodeValue && result.Message == "" {
		result.Message = "操作成功"
	}
	// 消息
	if result.Message != "" {
		ctx.Assign.Result[option.Response.MessageName] = result.Message
	}
	// 数据
	ctx.Assign.Result[option.Response.DataName] = result.Data

	// 扩展数据
	if result.Extra != nil {
		for key, value := range result.Extra {
			if key == option.Response.MessageName || key == option.Response.CodeName || key == option.Response.DataName {
				continue
			}
			ctx.Assign.Result[key] = value
		}
	}

	// 渲染
	ctx.Render(httpStatus, result.RenderType, result.OnlyOutData, ctx.Template, ctx.Assign)
	ctx.Abort()
}

// 错误输出
func (ctx *Context) AbortWithError(err interface{}) {
	var result *Result
	if err == nil {
		result = &Result{}
	} else if e, ok := err.(*Result); ok {
		result = e
	} else if e, ok := err.(error); ok {
		result = ErrorInternalServerError.Copy(e.Error())
	} else {
		result = ErrorInternalServerError.Copy(fmt.Sprintf("%s", err))
	}
	// 消息代码
	if result.Code == "" {
		result.Code = option.Response.FailCodeValue
	}
	SetErrResult(ctx.Context, result)
	// 是否使用普通消息输出
	if result.IsInfo {
		ctx.AbortWithMessage(result)
		return
	}
	ctx.Template = option.Response.ErrorTemplate
	ctx.AbortWithSuccess(result)
}

// html输出 （使用HTML渲染式）
func (ctx *Context) AbortWithHtml(result *Result) {
	if result == nil {
		result = &Result{}
	}
	result.RenderType = RenderTypeHTML
	ctx.AbortWithSuccess(result)
}

// JSON输出
func (ctx *Context) AbortWithJSON(result *Result) {
	if result == nil {
		result = &Result{}
	}
	result.RenderType = RenderTypeJSON
	ctx.AbortWithSuccess(result)
}

// 消息输出 （使用消息模版）
func (ctx *Context) AbortWithMessage(result *Result) {
	result.IsInfo = true
	ctx.Template = option.Response.MessageTemplate
	ctx.AbortWithSuccess(result)
}

// 未初始化Context情况下输出错误
func AbortWithError(c *gin.Context, err interface{}) {
	// 输出错误时发生错误
	defer func() {
		if r := recover(); r != nil {
			errMsg := fmt.Sprintf("错误输出时发生意外(%s)：%s", err, r)
			WriteErrStackLog(c, errMsg)
		}
	}()
	ctx := NewContext(c)
	ctx.AbortWithError(err)
}
